/**
 * 需要修改spring的配置文件，在sessionFactory中加入 
 * <property name="entityInterceptor" ref="autoInterceptor" />
 */
package com.bfsuolframework.core.dao.support;

import java.io.Serializable;
import java.lang.reflect.Field;

import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer;
import org.hibernate.type.Type;
import org.springframework.stereotype.Component;

@Component
public class AutoCountInterceptor extends EmptyInterceptor {   
	private static final long serialVersionUID = 1L;
	
	protected final static String JAVASSIST_IDENTIFIER = "_$$_javassist";   
     
    @Override 
    public void onDelete(Object entity, Serializable id, Object[] state,   
            String[] propertyNames, Type[] types) throws CallbackException {
        updateObjectCount(state, new CountOperation() {
            public int execute(int count) {   
                return count > 0 ? count - 1 : count;   
            }   
        });   
    }   
    
    @Override 
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException {   
    	updateObjectCount(state, new CountOperation(){
            public int execute(int count) {   
                return count + 1;   
            }   
        });   
        return false;   
    }   
  
    private void updateObjectCount(Object[] state, CountOperation operation) {   
        for (int i = 0; i < state.length; i++) {   
            if (state[i] != null) {   
                FieldTarget fieldTarget = getAutoCountField(state[i]);   
  
                if (fieldTarget != null) {   
                    Field field = fieldTarget.getField();   
  
                    Integer count = 0;   
                    try {   
                        Object target = fieldTarget.getTarget();   
                        field.setAccessible(true);  
                        count = operation.execute((Integer) field.get(target));   
                        field.set(target, count);   
  
                        state[i] = target;   
                    } catch (IllegalArgumentException e) {   
                        e.printStackTrace();   
                    } catch (IllegalAccessException e) {   
                        e.printStackTrace();   
                    }   
                }   
            }   
        }   
    }   
  
    private FieldTarget getAutoCountField(Object state) {   
        Class<?> clazz = state.getClass();   
  
        if (clazz == null) {   
            return null;   
        }   
  
        String s = clazz.getName();   
  
        Class<?> clazzToUse = null;   
        Object target = null;   
  
        FieldTarget fieldTarget = null;   
  
        if (s.contains(JAVASSIST_IDENTIFIER)) {   
            try {   
                clazzToUse = Class.forName(s.substring(0, s   
                        .indexOf(JAVASSIST_IDENTIFIER)));   
  
                Field[] ctFields = clazz.getDeclaredFields();   
                ctFields[1].setAccessible(true);   
                JavassistLazyInitializer javassistLazyInitializer = (JavassistLazyInitializer) ctFields[1]   
                        .get(state);   
                target = javassistLazyInitializer.getImplementation();   
            } catch (ClassNotFoundException e) {   
                e.printStackTrace();   
                return null;   
            } catch (IllegalArgumentException e) {   
                e.printStackTrace();   
                return null;   
            } catch (IllegalAccessException e) {   
                e.printStackTrace();   
                return null;   
            }   
        } else {   
            clazzToUse = clazz;   
            target = state;   
        }   
           
        if (clazzToUse != null) {   
            Field[] fields = clazzToUse.getDeclaredFields();   
  
            for (int i = 0; i < fields.length; i++) {   
                if (fields[i].isAnnotationPresent(AutoCount.class)) {
                    if (target != null) {   
                        try {   
                            Object value = getFieldValue(target, fields[i]   
                                    .getName());   
                            if (value == null)   
                                return null;   
                               
                            fields[i].setAccessible(true);   
                            fields[i].set(state, value);   
  
                            fieldTarget = new FieldTarget(fields[i], target);   
                        } catch (IllegalArgumentException e) {   
                            e.printStackTrace();   
                            return null;   
                        } catch (IllegalAccessException e) {   
                            e.printStackTrace();   
                            return null;   
                        }   
                    }   
  
                    return fieldTarget;   
                }   
            }   
        }   
  
        return null;   
    }   
  
    private interface CountOperation {   
        int execute(int count);   
    }   
  
    private Object getFieldValue(Object target, String field) {   
        Class<?> clazz = target.getClass();   
        try {   
            Field f = clazz.getDeclaredField(field);   
            f.setAccessible(true);   
            return f.get(target);   
        } catch (SecurityException e) {   
            e.printStackTrace();   
            return null;   
        } catch (NoSuchFieldException e) {   
            e.printStackTrace();   
            return null;   
        } catch (IllegalArgumentException e) {   
            e.printStackTrace();   
            return null;   
        } catch (IllegalAccessException e) {   
            e.printStackTrace();   
            return null;   
        }   
    }   
  
    private class FieldTarget {   
        public FieldTarget(Field field, Object target) {   
            super();   
            this.field = field;   
            this.target = target;   
        }   
  
        private Field field;   
  
        public Field getField() {   
            return field;   
        }   
  
        public Object getTarget() {   
            return target;   
        }   
  
        private Object target;   
    }   
}  

